/****************************************************************************
**
** This file is part of qPhotoTag, a photo keywording application
**
** Copyright (C) 2009 Marcell Lengyel <miketkf@gmail.com>
**
** GNU General Public License
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**
****************************************************************************/

// C ANSI includes.
extern "C"
{
#include <sys/stat.h>
#include <utime.h>
}
#include <cmath>

#include <QtDebug>
#include "photo.h"



Photo::Photo(const QString &filename)
{
	save_needed = false;
	fullpath = filename;
	image = Exiv2::ImageFactory::open(filename.toStdString());
	image->readMetadata();
	exif_data = image->exifData();
	iptc_data = image->iptcData();
        xmp_data = image->xmpData();
	// thumbnail
	Exiv2::ExifThumb thumbnail(exif_data);
	Exiv2::DataBuf data_buffer = thumbnail.copy();
	char *char_data = (char *)data_buffer.pData_;
	long len = data_buffer.size_;
	std::string data(len, ' ');
	for (long i = 0; i < len; ++i)
	{
		data[i] = char_data[i];
	}
	// thumbnail should be in data
	tn_data = data;
	tn_len = len;
        // camera maker
        camera_make = QString(getExifTagData("Exif.Image.Make"));
	// caption
        QSettings settings;
        if (settings.value("useIPTC", true).toBool()) {
            caption = getIptcTagString("Iptc.Application2.Caption", true);
        }
        // prefer IPTC over XMP
        if (caption.isEmpty() && settings.value("useXMP", true).toBool()) {
            caption = getXmpTagString("Xmp.dc.title", true);
        }
        lens_data.lens_model = QString(getExifTagData("Exif.Canon.0x0095"));
        long int nom;
        long int den;
        if (getExifTagRational("Exif.Photo.FocalLength", nom, den, 0))
            lens_data.focallength = (double(nom)) / den;
        else
            lens_data.focallength = 0;
        if (getExifTagRational("Exif.Photo.MaxApertureValue", nom, den, 0))
            lens_data.max_aperture = (double(nom)) / den;
        else {
            QByteArray val_ba = getExifTagData("Exif.CanonCs.MaxAperture");
            if (val_ba.size() == 2) {
                int val = std::abs(val_ba.at(1));
                lens_data.max_aperture = static_cast<double>(std::exp(std::log(2.0) * (double(val)/32) / 2));
            } else {
                lens_data.max_aperture = 0.0;
            }
        }
        if (getExifTagRational("Exif.Photo.FNumber", nom, den, 0))
            lens_data.fnumber = (double(nom)) / den;
        else if (getExifTagRational("Exif.Photo.ApertureValue", nom, den, 0))
            lens_data.fnumber = (double(nom)) / den;
        else
            lens_data.fnumber = 0.0;
        lens_data.lens_model_update = false;
        lens_data.focallength_update = false;
        lens_data.max_aperture_update = false;
        lens_data.fnumber_update = false;
        // save the original data too
        original_lens_data = lens_data;
}

void Photo::save_all()
{
    if (! save_needed)
        return;
    // check if file is read only
    QFileInfo finfo(fullpath);
    QFileInfo dinfo(finfo.path());
    if (!finfo.isWritable())
        qDebug() << "File '" << finfo.fileName().toAscii().constData() << "' is read-only. Metadata not saved." << endl;
    if (!dinfo.isWritable())
        qDebug() << "Dir '" << dinfo.filePath().toAscii().constData() << "' is read-only. Metadata not saved." << endl;
    // lens data
    if (camera_make == QString("Canon") && lens_data.lens_model_update && ! lens_data.lens_model.isEmpty()) {
        setExifTagString("Exif.Canon.0x0095", lens_data.lens_model);
    }
    if (lens_data.fnumber_update) {
        long num, den;
        convertToRational(lens_data.fnumber, &num, &den, 4);
        setExifTagRational("Exif.Photo.FNumber", num, den);
        setExifTagRational("Exif.Photo.ApertureValue", num, den);
    }
    if (lens_data.focallength_update) {
        long num, den;
        convertToRational(lens_data.focallength, &num, &den, 4);
        setExifTagRational("Exif.Photo.FocalLength", num, den);
    }
    if (lens_data.max_aperture_update) {
        long num, den;
        convertToRational(lens_data.max_aperture, &num, &den, 4);
        setExifTagRational("Exif.Photo.MaxApertureValue", num, den);
        if (camera_make == QString("Canon")) {
            int maxap = static_cast<int>(std::log(lens_data.max_aperture) / std::log(2.0) * 2 * 32);
            exif_data["Exif.CanonCs.MaxAperture"] = static_cast<uint16_t>(maxap);
        }
    }
    QSettings settings;
    if (settings.value("saveEncodingName", true).toBool()) {
        QString saved_encoding_name = settings.value("iptcEncoding", QString("")).toString();
        if (! saved_encoding_name.isEmpty())
            setIptcTagString("Iptc.Application2.SpecialInstructions", saved_encoding_name);
    }

    try {
        Exiv2::AccessMode mode;
        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
                                      (QFile::encodeName(fullpath)));
        image->readMetadata();
        mode = image->checkMode(Exiv2::mdExif);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
            image->setExifData(exif_data);
        mode = image->checkMode(Exiv2::mdIptc);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
            image->setIptcData(iptc_data);
        mode = image->checkMode(Exiv2::mdXmp);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
            image->setXmpData(xmp_data);

        // TODO: Warn if it was not possible to save either of these
        // NOTE: Don't touch access and modification timestamp of file.
        struct stat st;
        ::stat(QFile::encodeName(fullpath), &st);

        struct utimbuf ut;
        ut.modtime = st.st_mtime;
        ut.actime  = st.st_atime;

        image->writeMetadata();

        ::utime(QFile::encodeName(fullpath), &ut);
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot save metadata using Exiv2 " << e.what() << endl;
    }

}

QString Photo::get_caption() const
{
	return caption;
}

void Photo::set_caption(const QString &caption_text)
{
    QSettings settings;
    caption = caption_text;
    if (settings.value("useIPTC", true).toBool()) {
        if (caption_text.isEmpty())
            removeIptcTag("Iptc.Application2.Caption");
        else
            setIptcTagString("Iptc.Application2.Caption", caption_text);
    }
    if (settings.value("useXMP", true).toBool()) {
        if (caption_text.isEmpty())
            removeXmpTag("Xmp.dc.title");
        else
            setXmpTagString("Xmp.dc.title", caption_text);
    }
    save_needed = true;
}

void Photo::get_lens_data(LENS_DATA &lens_data)
{
    lens_data = this->lens_data;
    if (! this->lens_data.lens_model_update)
        lens_data.lens_model = this->original_lens_data.lens_model;
    if (! this->lens_data.max_aperture_update)
        lens_data.max_aperture = this->original_lens_data.max_aperture;
    if (! this->lens_data.fnumber_update)
        lens_data.fnumber = this->original_lens_data.fnumber;
    if (! this->lens_data.focallength_update)
        lens_data.focallength = this->original_lens_data.focallength;

}

void Photo::set_lens_data(LENS_DATA new_lens_data)
{
    // Only for Canon
    if (QString(getExifTagData("Exif.Image.Make")) == QString("Canon"))
    {
        this->lens_data.lens_model = new_lens_data.lens_model;
        this->lens_data.lens_model_update = new_lens_data.lens_model_update;
    }
    this->lens_data.fnumber = new_lens_data.fnumber;
    this->lens_data.fnumber_update = new_lens_data.fnumber_update;
    this->lens_data.focallength = new_lens_data.focallength;
    this->lens_data.focallength_update = new_lens_data.focallength_update;
    this->lens_data.max_aperture = new_lens_data.max_aperture;
    this->lens_data.max_aperture_update = new_lens_data.max_aperture_update;
    if (this->lens_data.fnumber != this->original_lens_data.fnumber ||
        this->lens_data.lens_model != this->original_lens_data.lens_model ||
        this->lens_data.focallength != this->original_lens_data.focallength ||
        this->lens_data.max_aperture != this->original_lens_data.max_aperture)
        save_needed = true;
}


std::string Photo::get_thumbnail()
{
	return tn_data;
}

bool Photo::has_thumbnail()
{
	return (tn_len > 0 ? true : false);
}

bool Photo::has_georeference()
{
        //return has_georef;
    bool retval = false;
    double lat;
    double lon;
    if (this->getGPSLatitudeNumber(&lat) && this->getGPSLongitudeNumber(&lon))
        retval = true;
    return retval;
}

QByteArray Photo::getExifTagData(const char* exifTagName) const
{
    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData exifData(exif_data);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
            char *s = new char[(*it).size()];
#if (EXIV2_TEST_VERSION(0,17,91))
            (*it).copy((Exiv2::byte*)s, Exiv2::bigEndian);
#else
            (*it).copy((Exiv2::byte*)s, exifData.byteOrder());
#endif
            QByteArray data(s, (*it).size());
            delete s;
            return data;
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot find Exif key " << exifTagName << " into image using Exiv2 " << endl;
    }

    return QByteArray();
}

bool Photo::getGPSLongitudeNumber(double *longitude) const
{
    try
    {
        double num, den, min, sec;
        *longitude=0.0;

        // Get the reference first.

        QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
        if (lngRef.isEmpty()) return false;

        // Longitude decoding.

        Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
        Exiv2::ExifData exifData(exif_data);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey2);
        if (it != exifData.end() && (*it).count() == 3)
        {
            num = (double)((*it).toRational(0).first);
            den = (double)((*it).toRational(0).second);
            if (den == 0)
                return false;
            *longitude = num/den;

            num = (double)((*it).toRational(1).first);
            den = (double)((*it).toRational(1).second);
            if (den == 0)
                return false;
            min = num/den;
            if (min != -1.0)
                *longitude = *longitude + min/60.0;

            num = (double)((*it).toRational(2).first);
            den = (double)((*it).toRational(2).second);
            if (den == 0)
                return false;
            sec = num/den;
            if (sec != -1.0)
                *longitude = *longitude + sec/3600.0;
        }
        else
            return false;

        if (lngRef[0] == 'W')
            *longitude *= -1.0;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot get Exif GPS tag using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::getGPSLatitudeNumber(double *latitude) const
{
    try
    {
        double num, den, min, sec;
        *latitude=0.0;

        // Get the reference first.

        QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
        if (latRef.isEmpty()) return false;

        // Latitude decoding.

        Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
        Exiv2::ExifData exifData(exif_data);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end() && (*it).count() == 3)
        {
            num = (double)((*it).toRational(0).first);
            den = (double)((*it).toRational(0).second);
            if (den == 0)
                return false;
            *latitude = num/den;

            num = (double)((*it).toRational(1).first);
            den = (double)((*it).toRational(1).second);
            if (den == 0)
                return false;
            min = num/den;
            if (min != -1.0)
                *latitude = *latitude + min/60.0;

            num = (double)((*it).toRational(2).first);
            den = (double)((*it).toRational(2).second);
            if (den == 0)
                return false;
            sec = num/den;
            if (sec != -1.0)
                *latitude = *latitude + sec/3600.0;
        }
        else
            return false;

        if (latRef[0] == 'S')
            *latitude *= -1.0;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot get Exif GPS tag using Exiv2 " << e.what() << endl;
    }

    return false;
}

QStringList Photo::get_keywords() const
{
    QStringList keywords;
    QSettings settings;
    if (settings.value("useIPTC", true).toBool()) {
        try
        {
            if (!iptc_data.empty())
            {
                QSettings settings;
                QString saved_encoding_name = settings.value("iptcEncoding", QString("latin2")).toString();
                QTextCodec::setCodecForCStrings(QTextCodec::codecForName(saved_encoding_name.toAscii()));
                Exiv2::IptcData iptcData(iptc_data);

                for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
                {
                    QString key = QString::fromLocal8Bit(it->key().c_str());

                    if (key == QString("Iptc.Application2.Keywords"))
                    {
                        QString val(it->toString().c_str());
                        if ( ! keywords.contains(val))
                            keywords.append(val);
                    }
                }
            }
        }
        catch( Exiv2::Error &e )
        {
            qDebug() << "Cannot get Iptc Keywords from image using Exiv2 " << e.what() << endl;
        }
    }
    if (settings.value("useXMP", true).toBool()) {
        try
        {
            if (!xmp_data.empty()) {
                Exiv2::XmpData xmpData(xmp_data);
                Exiv2::XmpKey keyword_key("Xmp.dc.subject");
                Exiv2::XmpData::const_iterator md = xmpData.findKey(keyword_key);
                while (md != xmpData.end())
                {
                    if ((md->typeId() == Exiv2::xmpBag) && (QLatin1String("Xmp.dc.subject") == (Exiv2::toString(md->key()).c_str()))) {
                        for (int i = 0; i < md->count(); i++) {
                            std::ostringstream os;
                            os << md->toString(i);
                            QString val = QString::fromUtf8(os.str().c_str());
                            if ( ! keywords.contains(val))
                                keywords.append(val);
                        }
                    }
                    md++;
                }
            }
       }
       catch( Exiv2::Error &e )
       {
            qDebug() << "Cannot get XMP Keywords from image using Exiv2 " << e.what() << endl;
       }
   }

   return keywords;
}

QString Photo::getIptcTagString(const char* iptcTagName, bool escapeCR) const
{
    try
    {
        QSettings settings;
        QString saved_encoding_name = settings.value("iptcEncoding", QString("latin2")).toString();
        QTextCodec::setCodecForCStrings(QTextCodec::codecForName(saved_encoding_name.toAscii()));
        Exiv2::IptcKey iptcKey(iptcTagName);
        Exiv2::IptcData iptcData(iptc_data);
        Exiv2::IptcData::iterator it = iptcData.findKey(iptcKey);
        if (it != iptcData.end())
        {
            std::ostringstream os;
            os << *it;
            QString tagValue = QString::fromAscii(os.str().c_str());

            if (escapeCR)
                tagValue.replace("\n", " ");

            return tagValue;
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot find Iptc key " << iptcTagName << " in image using Exiv2 " << e.what() << endl;
    }

    return QString();
}

QString Photo::getXmpTagString(const char* xmpTagName, bool escapeCR)
{
    try
    {
        Exiv2::XmpKey xmpKey(xmpTagName);
        Exiv2::XmpData xmpData(xmp_data);
        Exiv2::XmpData::iterator it = xmpData.findKey(xmpKey);
        if (it != xmpData.end())
        {
            if (it->typeId() == Exiv2::langAlt) {
                for (int i = 0; i < it->count(); i++) {
                    std::ostringstream os;
                    os << it->toString(i);
                    QString lang;
                    QString tagValue = QString::fromUtf8(os.str().c_str());
                    tagValue = detectLanguageAlt(tagValue, lang);
                    if (lang.compare("x-default")) {
                        if (escapeCR)
                            tagValue.replace("\n", " ");
                        return tagValue;
                    }
                }
            } else {
                std::ostringstream os;
                os << *it;
                QString tagValue = QString::fromUtf8(os.str().c_str());

                if (escapeCR)
                    tagValue.replace("\n", " ");
                return tagValue;
            }
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot find XMP key " << xmpTagName << " in image using Exiv2 " << e.what() << endl;
    }

    return QString();
}


bool Photo::setIptcTagString(const char *iptcTagName, const QString& value)
{
    try
    {
        QSettings settings;
        QString saved_encoding_name = settings.value("iptcEncoding", QString("latin2")).toString();
        QTextCodec::setCodecForCStrings(QTextCodec::codecForName(saved_encoding_name.toAscii()));
        iptc_data[iptcTagName] = std::string(value.toAscii().constData());
        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot set Iptc tag string into image using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::setXmpTagString(const char *xmpTagName, const QString& value)
{
    try
    {
        //const std::string &txt(value.toUtf8().constData());
        //Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::xmpText);
        //xmpTxtVal->read(txt);
        //xmp_data.add(Exiv2::XmpKey(xmpTagName), xmpTxtVal.get());
        xmp_data[xmpTagName] = std::string(value.toUtf8().constData());
        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot set XMP tag string into image using Exiv2 " << e.what() << endl;
    }

    return false;
}


bool Photo::set_keywords(const QStringList& oldKeywords, const QStringList& newKeywords)
{
    bool retval = false;
    QSettings settings;
    QStringList iptckeys; // exist in IPTC
    QStringList xmpkeys; // exist in XMP
    QStringList newkeys = newKeywords;
    if (settings.value("useIPTC", true).toBool()) {
        try
        {
            // Remove all old keywords.
            Exiv2::IptcData iptcData(iptc_data);
            Exiv2::IptcData::iterator it = iptcData.begin();
            QString saved_encoding_name = settings.value("iptcEncoding", QString("latin2")).toString();
            QTextCodec::setCodecForCStrings(QTextCodec::codecForName(saved_encoding_name.toAscii()));
            while(it != iptcData.end())
            {
                QString key = QString::fromLocal8Bit(it->key().c_str());
                QString val(it->toString().c_str());

                // Also remove new keywords to avoid duplicates. They will be added again below.
                if ( key == QString("Iptc.Application2.Keywords") &&
                     (oldKeywords.contains(val) || newKeywords.contains(val)) ) {
                    it = iptcData.erase(it);
                } else if ( key == QString("Iptc.Application2.Keywords")) {
                    iptckeys.append(val);
                    ++it;
                } else {
                    ++it;
                }
            };
            // Add new keywords. Note that Keywords Iptc tag is limited to 64 char
            Exiv2::IptcKey iptcTag("Iptc.Application2.Keywords");

            for (QStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
            {
                QString key = *it;
                key.truncate(64);

                Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
                val->read(key.toAscii().constData());
                iptcData.add(iptcTag, val.get());
            }
            iptc_data = iptcData;
            save_needed = true;
            retval = true;
        }
        catch( Exiv2::Error &e )
        {
            qDebug() << "Cannot set Iptc Keywords into image using Exiv2 " << e.what() << endl;
        }
    }
    if (settings.value("useXMP", true).toBool()) {
        try
        {
            QStringList currentKeywords;
            // Remove all old keywords.
            Exiv2::XmpData xmpData(xmp_data);
            Exiv2::XmpKey xmpKey("Xmp.dc.subject");
            Exiv2::XmpData::iterator it = xmpData.findKey(xmpKey);
            if (it != xmpData.end()) {
                if (it->typeId() == Exiv2::xmpBag)  {
                    for (int i = 0; i < it->count(); i++) {
                        std::ostringstream os;
                        os << it->toString(i);
                        QString val = QString::fromUtf8(os.str().c_str());
                        if (! oldKeywords.contains(val) && ! newKeywords.contains(val))
                            currentKeywords.append(val);
                    }
                }
                xmpData.erase(it);
            }
            xmpkeys = currentKeywords;
            currentKeywords.append(newKeywords);
            if (! currentKeywords.isEmpty()) {
                Exiv2::Value::AutoPtr xmpTxtBag = Exiv2::Value::create(Exiv2::xmpBag);
                for (QStringList::const_iterator it = currentKeywords.constBegin(); it != currentKeywords.constEnd(); ++it ) {
                    const std::string &txt((*it).toUtf8().constData());
                    xmpTxtBag->read(txt);
                }
                xmpData["Xmp.dc.subject"].setValue(xmpTxtBag.get());
                xmp_data = xmpData;
                save_needed = true;
                retval = true;
            }
        }
        catch( Exiv2::Error &e )
        {
            qDebug() << "Cannot set XMP Keywords into image using Exiv2 " << e.what() << endl;
        }
    }
    if (settings.value("useXMP", true).toBool() && settings.value("useIPTC", true).toBool()) {
        // Synchronize the keywords between IPTC and XMP
        Exiv2::IptcKey iptcTag("Iptc.Application2.Keywords");
        for (QStringList::iterator it = xmpkeys.begin(); it != xmpkeys.end(); ++it) {
            QString keyword = *it;
            keyword.truncate(64);
            if (! iptckeys.contains(keyword)) {
                // keyword exists in XMP, but not in IPTC. Let's add it to IPTC
                Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
                val->read(keyword.toAscii().constData());
                iptc_data.add(iptcTag, val.get());
                save_needed = true;
            }
        }
        // fill up the xmpkeys array with addition keywords (if any) from iptckeys
        for (QStringList::iterator it = iptckeys.begin(); it != iptckeys.end(); ++it) {
            QString keyword = *it;
            if (! xmpkeys.contains(keyword))
                xmpkeys.append(keyword);
        }
        // add the new stuff to xmpkeys
        xmpkeys.append(newKeywords);
        if (! xmpkeys.isEmpty()) {
            // replace the old set of keywords with the new list
            Exiv2::Value::AutoPtr xmpTxtBag = Exiv2::Value::create(Exiv2::xmpBag);
            for (QStringList::iterator it = xmpkeys.begin(); it != xmpkeys.end(); ++it) {
                const std::string &txt((*it).toUtf8().constData());
                xmpTxtBag->read(txt);
            }
            xmp_data["Xmp.dc.subject"].setValue(xmpTxtBag.get());
            save_needed = true;
        }
    }

    return retval;
}

QString Photo::get_fullpath() const
{
    return fullpath;
}

bool Photo::setGPSInfo(double altitude, double latitude, double longitude)
{
    try
    {
        // In first, we need to clean up all existing GPS info.
        removeGPSInfo();

        char scratchBuf[100];
        long int nom, denom;
        long int deg, min;

        // Do all the easy constant ones first.
        // GPSVersionID tag: standard says is should be four bytes: 02 00 00 00
        // (and, must be present).
        Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
        value->read("2 0 0 0");
        exif_data.add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get());

        // Datum: the datum of the measured data. If not given, we insert WGS-84.
        exif_data["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";

        // Now start adding data.
        // ALTITUDE.
        if (altitude != 0) {
            // Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
            value = Exiv2::Value::create(Exiv2::unsignedByte);
            if (altitude >= 0) value->read("0");
            else               value->read("1");
            exif_data.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());

            // And the actual altitude, as absolute value..
            convertToRational(fabs(altitude), &nom, &denom, 4);
            snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
            exif_data["Exif.GPSInfo.GPSAltitude"] = scratchBuf;
        }

        // LATTITUDE
        // Latitude reference: "N" or "S".
        if (latitude < 0)
        {
            // Less than Zero: ie, minus: means
            // Southern hemisphere. Where I live.
            exif_data["Exif.GPSInfo.GPSLatitudeRef"] = "S";
        }
        else
        {
            // More than Zero: ie, plus: means
            // Northern hemisphere.
            exif_data["Exif.GPSInfo.GPSLatitudeRef"] = "N";
        }
        // Now the actual lattitude itself.
        // This is done as three rationals.
        // I choose to do it as:
        //   dd/1 - degrees.
        //   mmmm/100 - minutes
        //   0/1 - seconds
        // Exif standard says you can do it with minutes
        // as mm/1 and then seconds as ss/1, but its
        // (slightly) more accurate to do it as
        //  mmmm/100 than to split it.
        // We also absolute the value (with fabs())
        // as the sign is encoded in LatRef.
        // Further note: original code did not translate between
        //   dd.dddddd to dd mm.mm - that's why we now multiply
        //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
        deg   = (int)floor(fabs(latitude)); // Slice off after decimal.
        min   = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
        snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
        exif_data["Exif.GPSInfo.GPSLatitude"] = scratchBuf;

        // LONGITUDE
        // Longitude reference: "E" or "W".
        if (longitude < 0)
        {
            // Less than Zero: ie, minus: means
            // Western hemisphere.
            exif_data["Exif.GPSInfo.GPSLongitudeRef"] = "W";
        }
        else
        {
            // More than Zero: ie, plus: means
            // Eastern hemisphere. Where I live.
            exif_data["Exif.GPSInfo.GPSLongitudeRef"] = "E";
        }

        // Now the actual longitude itself.
        // This is done as three rationals.
        // I choose to do it as:
        //   dd/1 - degrees.
        //   mmmm/100 - minutes
        //   0/1 - seconds
        // Exif standard says you can do it with minutes
        // as mm/1 and then seconds as ss/1, but its
        // (slightly) more accurate to do it as
        //  mmmm/100 than to split it.
        // We also absolute the value (with fabs())
        // as the sign is encoded in LongRef.
        // Further note: original code did not translate between
        //   dd.dddddd to dd mm.mm - that's why we now multiply
        //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
        deg   = (int)floor(fabs(longitude)); // Slice off after decimal.
        min   = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
        snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
        exif_data["Exif.GPSInfo.GPSLongitude"] = scratchBuf;
        has_georef = true;
        save_needed = true;
        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot set Exif GPS tag using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::removeGPSInfo()
{
    try
    {
        QStringList gpsTagsKeys;

        for (Exiv2::ExifData::iterator it = exif_data.begin();
             it != exif_data.end(); ++it)
        {
            QString key = QString::fromLocal8Bit(it->key().c_str());

            if (key.section(".", 1, 1) == QString("GPSInfo"))
                gpsTagsKeys.append(key);
        }

        for(QStringList::const_iterator it2 = gpsTagsKeys.constBegin(); it2 != gpsTagsKeys.constEnd(); ++it2)
        {
            Exiv2::ExifKey gpsKey((*it2).toAscii().constData());
            Exiv2::ExifData::iterator it3 = exif_data.findKey(gpsKey);
            if (it3 != exif_data.end())
                exif_data.erase(it3);
        }

        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot remove Exif GPS tag using Exiv2  " << e.what() << endl;
    }

    return false;
}

void Photo::convertToRational(double number, long int* numerator, long int* denominator, int rounding)
{
    // This function converts the given decimal number
    // to a rational (fractional) number.
    //
    // Examples in comments use Number as 25.12345, Rounding as 4.

    // Split up the number.
    double whole      = trunc(number);
    double fractional = number - whole;

    // Calculate the "number" used for rounding.
    // This is 10^Digits - ie, 4 places gives us 10000.
    double rounder = pow(10.0, rounding);

    // Round the fractional part, and leave the number
    // as greater than 1.
    // To do this we: (for example)
    //  0.12345 * 10000 = 1234.5
    //  floor(1234.5) = 1234 - now bigger than 1 - ready...
    fractional = round(fractional * rounder);

    // Convert the whole thing to a fraction.
    // Fraction is:
    //     (25 * 10000) + 1234   251234
    //     ------------------- = ------ = 25.1234
    //           10000            10000
    double numTemp = (whole * rounder) + fractional;
    double denTemp = rounder;

    // Now we should reduce until we can reduce no more.

    // Try simple reduction...
    // if   Num
    //     ----- = integer out then....
    //      Den
    if (trunc(numTemp / denTemp) == (numTemp / denTemp))
    {
        // Divide both by Denominator.
        numTemp /= denTemp;
        denTemp /= denTemp;
    }

    // And, if that fails, brute force it.
    while (1)
    {
        // Jump out if we can't integer divide one.
        if ((numTemp / 2) != trunc(numTemp / 2)) break;
        if ((denTemp / 2) != trunc(denTemp / 2)) break;
        // Otherwise, divide away.
        numTemp /= 2;
        denTemp /= 2;
    }

    // Copy out the numbers.
    *numerator   = (int)numTemp;
    *denominator = (int)denTemp;
}

bool Photo::getExifTagRational(const char *exifTagName, long int &num, long int &den, int component) const
{
    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData exifData(exif_data);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
            num = (*it).toRational(component).first;
            den = (*it).toRational(component).second;
            return true;
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot find Exif Rational value from key " << exifTagName << "into image using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::setExifTagString(const char *exifTagName, const QString& value)
{
    try
    {
        exif_data[exifTagName] = std::string(value.toAscii().constData());
        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot set Exif tag string into image using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::setExifTagRational(const char *exifTagName, long int num, long int den)
{
    try
    {
        exif_data[exifTagName] = Exiv2::Rational(num, den);
        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot set Exif tag rational value into image using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::removeIptcTag(const char *iptcTagName)
{
    try
    {
        Exiv2::IptcData::iterator it = iptc_data.begin();
        while(it != iptc_data.end())
        {
            QString key = QString::fromLocal8Bit(it->key().c_str());

            if (key == QString(iptcTagName))
                it = iptc_data.erase(it);
            else
                ++it;
        };

        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot remove Iptc tag using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::removeXmpTag(const char *xmpTagName)
{
    try
    {
        Exiv2::XmpKey xmpKey(xmpTagName);
        Exiv2::XmpData::iterator it = xmp_data.findKey(xmpKey);
        if (it != xmp_data.end())
        {
            xmp_data.erase(it);
            return true;
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot remove Xmp tag using Exiv2 " << e.what() << endl;
    }

    return false;
}

QString Photo::detectLanguageAlt(const QString &value, QString& lang)
{
    // Ex. from an Xmp tag Xmp.tiff.copyright: "lang="x-default" (c) Gilles Caulier 2007"

    if (value.size() > 6 && value.startsWith(QString("lang=\"")))
    {
        int pos = value.indexOf(QString("\""), 6);
        if (pos != -1)
        {
            lang = value.mid(6, pos-6);
            return (value.mid(pos+2));
        }
    }

    lang = QString();
    return value;
}
